import os
import repo_path
import logging
import copy

import repo_path
import dep_graph.cache
import build_file.graph

logger = logging.getLogger(__name__)
graph_filename = 'bin/__graph__/newgraph.db'


def Error(Exception):
  """Config exception base class."""

def InvalidVariantException(Error):
  """Error occured parsing variants."""
  def __init__(self, name):
    super().__init__()
    self.name = name

def OpenGraphDatabase(clean_graph=False):
  """Opens the database holding the graph."""
  repo_path.ensure_path('bin/__graph__')
  return dep_graph.cache.FileBackedCachedGraph(graph_filename,
                                               clean_graph)


class RepoConfig(object):
  def __init__(self, fname):
    (self._BUILD_ROOT, self._rule_adjust,
         self._lib_dir) = repo_path.getPathConsts(fname)
    self._builtin_files = set()
    self._builtin_files.add((os.path.realpath(self._lib_dir),  'gen_rules'))
    self._files = set()
    self._config_dependency_files = set(('.build_config',))
    self._config_keys = {}

  def variants(self):
    """Returns the variants key from .build_config."""
    if 'variants' in self._config_keys:
      return self._config_keys['variants']
    return ['release']

  def __get_default_variant(self, name):
    """Returns the provided default_variant key from .build_config."""
    if name in self._config_keys:
      variant = self._config_keys[name]
      valid_variant = True
      valid_variant &= type(variant) == list
      for v in variant:
        valid_variant &= type(v) == str
      if valid_variant:
        return self._config_keys[name]
      else:
        raise InvalidVariantException(name)
    return ['release']

  def run_variants(self):
    """Returns the run_variant key from .build_config."""
    return self.__get_default_variant('run_variants')

  def default_variants(self):
    """Returns the default_variants key from .build_config."""
    return self.__get_default_variant('default_variants')

  def bin_directory(self):
    """Computes the bin directory path."""
    return 'bin'

  def output_directory(self, variant):
    """Computes the output folder for the provided variant."""
    if 'variants' in self._config_keys:
      return os.path.join(self.bin_directory(), variant + '-build')
    else:
      return self.bin_directory()

  def get_graph_filename(self):
    """ returns string    path to graph."""
    return graph_filename 

  def ensure_graph_directory(self):
    """Ensures that the graph directory has been created."""
    repo_path.ensure_path('bin/__graph__')

  def get_option(self, variant, option_name, default):
    """Returns an option value for a variant, with a default.

    Args:
      variant: string, The variant.
      option_name: string, The name of the option.
      default: The value to return if nothing matches.
    """
    if option_name in self._config_keys:
      my_option = self._config_keys[option_name]
      if type(my_option) == dict:
        if variant not in my_option:
          return default
        return copy.deepcopy(my_option[variant])
      else:
        return copy.deepcopy(my_option)
    else:
      return default

  @property
  def files(self):
    """Returns the files to load into the BUILD file environment.

    Returns:
      set(string), the set of files.
    """
    return self._files

  @property
  def builtin_files(self):
    """Returns the built in files to load into the BUILD file environment.

    Returns:
      set(tuple(string, string)), the set repository path, module
    """
    return self._builtin_files

  @property
  def config_dependency_files(self):
    """Returns the set of files that when changed invalidate all the graph.

    Returns:
      set(string), the set of files.
    """
    return self._config_dependency_files

  @property
  def lib_dir(self):
    """Returns the directory where the build tool is stored."""
    return self._lib_dir

  def names_to_keys(self, filenames, variant, graph=None):
    """Add a build file graph, if you need to reference a :all or a ... """
    for filename in filenames:
      keyStream = repo_path.wildcardToKeyIter(filename, variant, self._rule_adjust, graph)
      if keyStream:
        for key in keyStream:
          yield key
      else:
        yield repo_path.pathToKey(filename, variant, self._rule_adjust)

  def load_config_filelist(self):
    """Parses config from //.build_config."""

    # The //.build_config file has a bunch of calls to load_file(...)
    # Load them by execing the file with a load_file method defined which
    # writes them all down.
    self._config_keys['BUILD_ROOT'] = self._BUILD_ROOT
    self._config_keys['rule_adjust'] = self._rule_adjust
    self._config_keys['lib_dir'] = self._lib_dir

    self._config_keys['load_file'] = self._load_file
    try:
      byte_code = compile(open('.build_config').read(), '//.build_config', 'exec')
    except IOError:
      raise repo_path.Error('Problem reading .build_config')
    exec(byte_code, self._config_keys)

    del self._config_keys['load_file']

  def _load_file(self, fname):
    """Tracks the files tracked by load_file."""
    self._files.add(fname)
    self._config_dependency_files.add(fname)
